package com.config;

import com.authentication.SimpleOauth2RequestRepository;
import com.authentication.filter.Oauth2ExceptionTranslatorFilter;
import com.authentication.filter.RequireLoginFilter;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.web.*;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.savedrequest.CookieRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.filter.RequestContextFilter;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;

import static com.common.Oauth2Constant.authorizationEndpointUri;
import static org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
import static org.springframework.security.oauth2.core.AuthenticationMethod.HEADER;
import static org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE;
import static org.springframework.security.oauth2.core.ClientAuthenticationMethod.CLIENT_SECRET_POST;

@Configuration
public class ClientLoginConfig {

    public static final String OAUTH_2_SWAP_CODE_TO_TOKEN = "/oauth2/swapCodeToToken";
    public static final String AUTHORIZATION_SERVER_ADDRESS = "http://localhost:10000";
    public static final String RESOURCE_SERVER_ADDRESS = "http://localhost:12000";

    public static final String LOGIN_PAGE = "/login/page";

    private final ServerProperties serverProperties;

    public ClientLoginConfig(ServerProperties serverProperties) {
        this.serverProperties = serverProperties;
    }

    @Bean
    public DelegatingFilterProxy delegatingFilterProxy() {
        DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy();
        delegatingFilterProxy.setTargetBeanName("filterChainProxy");
        return delegatingFilterProxy;
    }

    @Bean
    public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxyRegistrationBean() {
        FilterRegistrationBean<DelegatingFilterProxy> result = new FilterRegistrationBean<>(delegatingFilterProxy());

        result.addUrlPatterns("/**");

        return result;
    }

    @Bean
    public FilterChainProxy filterChainProxy() {
        return new FilterChainProxy(
                Arrays.asList(
                        redirectUserLoginChain(),
                        code2TokenAuthorizationChain(),
                        loginPageFilterChain(),
                        anyRequestNeedAuthenticationChain()
                )
        );
    }

    /**
     * 用于渲染登陆页面
     * 具体选择哪一种 oauth2 登陆
     *
     * @return
     */
    @Bean
    public SecurityFilterChain loginPageFilterChain() {
        DefaultLoginPageGeneratingFilter loginPage = new DefaultLoginPageGeneratingFilter();

        loginPage.setLoginPageUrl(LOGIN_PAGE);

        HashMap<String, String> authenticationUrlToClientName = new HashMap<>();

        clientRegistrationRepository().iterator().forEachRemaining(clientRegistration -> {
            authenticationUrlToClientName.put(
                    DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/" + clientRegistration.getRegistrationId(),
                    clientRegistration.getRegistrationId()
            );
        });


        loginPage.setOauth2AuthenticationUrlToClientName(authenticationUrlToClientName);
        loginPage.setOauth2LoginEnabled(true);
        return new DefaultSecurityFilterChain(
                new AntPathRequestMatcher(LOGIN_PAGE),
                loginPage
        );
    }

    @Bean
    public SecurityFilterChain anyRequestNeedAuthenticationChain() {
        LoginUrlAuthenticationEntryPoint authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(
                LOGIN_PAGE
        );
        return new DefaultSecurityFilterChain(
                new AntPathRequestMatcher("/**"),
                Arrays.asList(
                        new ExceptionTranslationFilter(
                                authenticationEntryPoint,
                                requestCache()
                        ),


                        new SecurityContextHolderFilter(securityContextRepository()),
                        new RequireLoginFilter(),
                        new Oauth2ExceptionTranslatorFilter(authenticationEntryPoint,requestCache())
                )
        );
    }

    @Bean
    public RequestCache requestCache() {
        return new CookieRequestCache();
    }


    /**
     * 把用户重定向到 authorization server
     *
     * @return
     */
    @Bean
    public SecurityFilterChain redirectUserLoginChain() {

   /*     HttpSecurity http = new HttpSecurity();

        http.oauth2Client().authorizationCodeGrant()
                .and().;*/


        OAuth2AuthorizationRequestRedirectFilter oAuth2AuthorizationRequestRedirectFilter = new OAuth2AuthorizationRequestRedirectFilter(
                clientRegistrationRepository(),
                DEFAULT_AUTHORIZATION_REQUEST_BASE_URI
        );

        oAuth2AuthorizationRequestRedirectFilter.setAuthorizationRequestRepository(
                authorizationRequestRepository()
        );


        return new DefaultSecurityFilterChain(
                new AntPathRequestMatcher(DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/*"),
                Arrays.asList(

                        new ExceptionTranslationFilter(new HttpStatusEntryPoint(HttpStatus.FORBIDDEN)),
                        oAuth2AuthorizationRequestRedirectFilter
                )
        );
    }

    @Bean
    public CharacterEncodingFilter characterEncodingFilter() {
        return new CharacterEncodingFilter(StandardCharsets.UTF_8.name());
    }

    @Bean
    public InMemoryClientRegistrationRepository clientRegistrationRepository() {

        ClientRegistration steam = ClientRegistration.withRegistrationId("微信")
                .authorizationGrantType(AUTHORIZATION_CODE)
                .clientId("pdd")
                .clientSecret("pdd_password")
                .redirectUri("http://localhost:" + serverProperties.getPort() + OAUTH_2_SWAP_CODE_TO_TOKEN)
                .clientName("拼多多")
                .userNameAttributeName("sub")

                .authorizationUri(AUTHORIZATION_SERVER_ADDRESS + authorizationEndpointUri)
                .tokenUri(AUTHORIZATION_SERVER_ADDRESS + "/oauth2/token")
                .scope("user_id")
                .clientAuthenticationMethod(CLIENT_SECRET_POST)
                .userInfoUri(RESOURCE_SERVER_ADDRESS + "/user/info")
                .userInfoAuthenticationMethod(HEADER)
                .build();


        return new InMemoryClientRegistrationRepository(steam);
    }

    /**
     * 使用 code 到 authorization server 换取 token
     *
     * @return
     */
    @Bean
    public SecurityFilterChain code2TokenAuthorizationChain() {

        OAuth2LoginAuthenticationFilter oAuth2LoginAuthenticationFilter = new OAuth2LoginAuthenticationFilter(
                clientRegistrationRepository(),
                authorizedClientRepository(),
                OAUTH_2_SWAP_CODE_TO_TOKEN
        );

        oAuth2LoginAuthenticationFilter.setAuthorizationRequestRepository(
                // 存储 第一次发起 oauth2 认证 的请求信息
                authorizationRequestRepository()
        );

        oAuth2LoginAuthenticationFilter.setAuthenticationManager(
//                new SimpleInMemoryAuthenticationManager()
                new ProviderManager(

                        // 集成的功能
                        // 1、使用 authorization code 换取 token (access token 和 refresh token)
                        // 2、使用 access token 换取 userinfo
                        new OAuth2LoginAuthenticationProvider(
                                new DefaultAuthorizationCodeTokenResponseClient(),
                                new DefaultOAuth2UserService()
                        )
                )
        );

        oAuth2LoginAuthenticationFilter.setSecurityContextRepository(
                // 登陆成功之后，用于存储用户的认证信息
                securityContextRepository()
        );

        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();

//      认证成功之后能跳转未登录时访问的网页
        successHandler.setRequestCache(requestCache());

        oAuth2LoginAuthenticationFilter.setAuthenticationSuccessHandler(
                successHandler
        );

        /*HttpSecurity http = new HttpSecurity();

        http.oauth2Login(oauth2->{
            oauth2.authorizationEndpoint();
            oauth2.userInfoEndpoint()
        });*/

        return new DefaultSecurityFilterChain(
                new AntPathRequestMatcher(OAUTH_2_SWAP_CODE_TO_TOKEN),
                Arrays.asList(
                        new ExceptionTranslationFilter(
                                new LoginUrlAuthenticationEntryPoint(LOGIN_PAGE)
                        ),
                        oAuth2LoginAuthenticationFilter
                )
        );
    }


    /**
     * 用于自动管理 oauth2 user 的 refresh token 换取 access token
     * @return
     */
    @Bean
    public OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager() {

        return new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository(),
                authorizedClientRepository()
        );
    }


    /**
     * 存储已经通过 oauth2 认证的用户 认证授权信息的仓库
     * */
    @Bean
    public OAuth2AuthorizedClientRepository authorizedClientRepository() {
        return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(
                new InMemoryOAuth2AuthorizedClientService(
                        clientRegistrationRepository()
                )
        );
    }

    /**
     * 存储用户认证授权信息的仓库
     * @return
     */
    @Bean
    public SecurityContextRepository securityContextRepository() {
        return new HttpSessionSecurityContextRepository();
    }

    /**
     * 授权请求仓库
     * 记录第一次发起 oauth2 认证 的 state
     * 当有 code 传入时，判断 此请求 是否有第一次发起 oauth2 认证 对应的请求
     *
     * @return
     */
    @Bean
    public AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository() {
        return new SimpleOauth2RequestRepository();
    }
}
